#region File information

/**
 * File: SquadronGame.cs
 * Date: 08-03-2008
 * Auth: Jan-Jaap Severs
 * 
 * Desc: The main game class. Also handles all networking activity.
 */

#endregion

#region Using statements

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Gremlin.Components;
using Squadron.Networking;

#endregion

namespace Squadron
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class SquadronGame : Microsoft.Xna.Framework.Game
    {
        #region Member variables

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        /// The current game's network session.
        NetworkSession networkSession;

        /// The maximum number of local gamers in the session.        
        const int maxLocalGamers = 1;

        /// The maximum number of total gamers in the session.
        const int maxGamers = 16;

        PacketWriter packetWriter = new PacketWriter();
        PacketReader packetReader = new PacketReader();

        /// A string to keep track of the most recent error message.
        /// TODO: Replace by some kind of logging system.
        string errorMessage;

        /// The game world.
        World world;

        HUD hud;

        Gremlin.Components.Console console;
        #endregion

        #region Initialization

        public SquadronGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";            

            // Add a camera so we can look around the world.
            CameraProvider cameraProvider = new CameraProvider();
            Services.AddService(typeof(ICameraService), cameraProvider);

            // Add the GameServicesComponent; necessary for network games.
            Components.Add(new GamerServicesComponent(this));
         
            Components.Add(new FPS(this));

            hud = new HUD(this);
            Components.Add(hud);

            world = new World(this);

            console = new Gremlin.Components.Console(this);
            Components.Add(console);
        }

        public void test(string c)
        {
            console.AddLine("Delegate succes");
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here      
            //CollisionTest();
            base.Initialize();
            
            console.SetRenderDimensions(new Vector2(0.01f, 0.01f), new Vector2(0.5f, 0.4f));
            console.AddLine("Entered");
            console.ToggleKey = Keys.Tab;
            console.commandEvent += test;

            world.Initialize(Content);
        }

        private void CollisionTest()
        {
            Components.Add(new Gremlin.Collision.TestScene(this));
        }

        #endregion

        #region Creating and joining sessions

        void CreateNetworkSession()
        {
            try
            {
                // Try to create a networksession.
                networkSession = NetworkSession.Create(NetworkSessionType.SystemLink,
                                                       maxLocalGamers, maxGamers);

                // Hook the event handlers to the networksession's events.
                HookSessionEvents();
            }
            catch (Exception e)
            {
                errorMessage = e.Message;                
            }
        }

        void JoinNetworkSession()
        {
            try
            {
                // Search for sessions.
                using (AvailableNetworkSessionCollection availableSessions =
                            NetworkSession.Find(NetworkSessionType.SystemLink,
                                                maxLocalGamers, null))
                {
                    if (availableSessions.Count == 0)
                    {
                        errorMessage = "No network sessions found.";
                        return;
                    }

                    // Join the first session we found.
                    // TODO: Implement a lobby that displays all available games.
                    networkSession = NetworkSession.Join(availableSessions[0]);

                    HookSessionEvents();
                }
            }
            catch (Exception e)
            {
                errorMessage = e.Message;
            }
        }

        #endregion

        #region Network event handlers

        /// <summary>
        /// Hooks the event handlers to the networkSession's events.
        /// </summary>
        void HookSessionEvents()
        {
            networkSession.GamerJoined += GamerJoinedEventHandler;
            networkSession.SessionEnded += SessionEndedEventHandler;
        }

        /// <summary>
        /// This event handler will be called whenever a new gamer joins the session.
        /// We use it to allocate a Ship object, and associate it with the new gamer.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
        {
            int gamerIndex = networkSession.AllGamers.IndexOf(e.Gamer);

            if (!e.Gamer.IsLocal)            
                world.AddRemoteShip((UInt16)gamerIndex);              

            // Inform the newly-joined client of his/her ID.
            ClientJoinedPacket packet = new ClientJoinedPacket((UInt16)(gamerIndex));
            packet.Write(packetWriter, SendDataOptions.InOrder, (LocalNetworkGamer)networkSession.Host,
                e.Gamer);
        }

        /// <summary>
        /// This event handler notifies us when the network session has ended.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
        {
            errorMessage = e.EndReason.ToString();

            networkSession.Dispose();
            networkSession = null;
        }

        #endregion

        #region Load & unload content

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        #endregion

        #region Update

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (networkSession == null)
            {
                // TODO: Implement lobby for hosting and joining games.

                // TEMP:
                // Sign in to profile.
                if (Gamer.SignedInGamers.Count == 0)
                {
                    // If there are no profiles signed in, we cannot proceed.
                    // Show the Guide so the user can sign in.
                    try
                    {
                        Guide.ShowSignIn(maxLocalGamers, false);
                    }
                    catch (Exception e)
                    {
                        errorMessage = e.Message;
                    }
                }
                else
                {
                    KeyboardState kbState = Keyboard.GetState();

                    if (kbState.IsKeyDown(Keys.C))
                    {
                        // Start a server.
                        CreateNetworkSession();
                    }
                    else if (kbState.IsKeyDown(Keys.J))
                    {
                        // Join a server.
                        JoinNetworkSession();
                    }
                }
            }
            else
            {
                // If we are in a network session, update it.
                UpdateNetworkSession(gameTime);
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// Updates the network session every frame.
        /// </summary>
        void UpdateNetworkSession(GameTime gameTime)
        {
            // Make sure the session has not ended.
            if (networkSession == null)
            {
                return;
            }  

            // Read any incoming network packets.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                ReadPackets(gamer);
            }

            // Read inputs for locally controlled ships, and send them to the server.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                UpdateLocalGamer(gamer, gameTime);
            }

            // If we are the server, update all the ships and transmit
            // their latest positions back out over the network.
            if (networkSession.IsHost)
            {
                UpdateServer(gameTime);
            }

            // Pump the underlying session object.
            networkSession.Update();

                      
        }

        /// <summary>
        /// This function collects input given by the local player and sends it to the server.
        /// </summary>
        /// <param name="gamer">The local gamer.</param>
        void UpdateLocalGamer(LocalNetworkGamer gamer, GameTime gameTime)
        {           
            // Only send if we are not the server. There is no point sending packets
            // to ourselves, because we already know what they will contain!
            if (!networkSession.IsHost)
            {              
                ClientPacket clientPacket = world.GetClientPacket();
                if (clientPacket != null)
                    clientPacket.Write(packetWriter, SendDataOptions.InOrder, gamer, networkSession.Host);
            }
            else                
                world.UpdateHostLocalShip();

            world.Update(gameTime);

            hud.Life = world.LocalShipLife;
        }
      
        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        void UpdateServer(GameTime gameTime)
        {          
            // Send the combined data for all ships to everyone in the session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;            
            
            // Get the current world state.
            WorldData worldDataPacket = world.GetWorldData();
            worldDataPacket.Write(packetWriter, SendDataOptions.InOrder, server);
        }

        #endregion

        #region Packet reading & processing

        /// <summary>
        /// Reads incoming packets from the network stream.
        /// </summary>
        /// <param name="gamer"></param>
        void ReadPackets(LocalNetworkGamer gamer)
        {
            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                // Read the packet type.
                PacketType packetType = (PacketType)packetReader.ReadInt32();

                switch (packetType)
                {
                    case PacketType.WorldData:
                        WorldData worldData = WorldData.Read(gamer, packetReader);

                        if (networkSession.IsHost)
                            break;

                        world.ProcessWorldDataPacket(worldData);

                        break;

                    case PacketType.PlayerInput:
                        ClientPacket clientPacket = ClientPacket.Read(gamer, packetReader);

                        world.ProcessClientInputPacket(clientPacket);

                        break;

                    case PacketType.ClientJoined:
                        ClientJoinedPacket clientJoined = ClientJoinedPacket.Read(gamer, packetReader);

                        world.ProcessClientJoinedPacket(clientJoined);

                        break;
                }
            }
        }      

        #endregion

        #region Rendering

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            if (networkSession != null)
                world.Draw(gameTime);

            base.Draw(gameTime);
        }

        #endregion
    }

    #region Program entry point

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main(string[] args)
        {
            using (SquadronGame game = new SquadronGame())
            {
                game.Run();
            }
        }
    }

    #endregion
}
